package com.qingzhou.gateway.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import com.qingzhou.common.core.constants.HttpConstant;
import com.qingzhou.common.core.utils.ContextUtil;
import com.qingzhou.common.core.web.api.ApiCode;
import com.qingzhou.common.security.utils.SecurityUtil;
import com.qingzhou.gateway.config.properties.GatewayProperties;
import com.qingzhou.gateway.config.properties.Predicate;
import com.qingzhou.common.core.web.api.ApiCodes;
import com.qingzhou.gateway.config.properties.Route;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import org.noear.solon.Utils;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.Context;
import org.noear.solon.cloud.utils.http.HttpUtils;
import org.noear.solon.core.handle.Gateway;
import org.noear.solon.core.handle.Result;
import org.noear.solon.core.handle.UploadedFile;
import org.noear.solon.core.util.PathAnalyzer;

import java.nio.charset.Charset;
import java.util.List;

/**
 * 接口网关
 * @author xm
 */
@Slf4j
@Component
@Mapping("/**")
public class ApiGateway extends Gateway {

    @Override
    protected void register() {

//        before(new StartHandler()); //开始计时
//        before(new ParamsParseHandler()); //参数解析
//        before(new ParamsSignCheckHandler(new Md5Encoder())); //参数签名较验
//        before(new ParamsRebuildHandler(new AesDecoder())); //参数重构
//        before(new ParamsNeedCheckHandler("g_lang"));//参数必要性检查//即公共参数
//        before(new ParamsLocaleHandler());

//        after(new OutputBuildHandler(new AesEncoder())); //输出构建
//        after(new OutputSignHandler(new Md5Encoder())); //输出签名
//        after(new OutputHandler()); //输出
//        after(new EndBeforeLogHandler()); //日志
//        after(new EndHandler("api")); //结束计时

        // 添加一个处理类（带 @Mapping 的函数会注册进来）
        add(HandlerMapping.class);
    }

    /**
     * 渲染（response）
     * @param obj
     * @param ctx
     * @throws Throwable
     */
    @Override
    public void render(Object obj, Context ctx) throws Throwable {
        super.render(obj, ctx);
    }

    public static class HandlerMapping {

        @Inject
        GatewayProperties gatewayProperties;

        // 没有加映射值时，将做为默认处理，当只有默认处理时，将接收所有请求
        @Mapping
        public Object handler(Context ctx) {
            return route(ctx);
        }

        /**
         * 路由转发
         * @param ctx
         * @return
         */
        private Object route(Context ctx) {
            try {
                if(StrUtil.isEmpty(ctx.path())) {
                    throw ApiCodes.CODE_404;
                }

                // 1、检测请求，并根据路径匹配服务名
                Route route = null;
                Predicate predicate = null;
                for (Route r : gatewayProperties.getRoutes()) {
                    predicate = r.getPredicates().stream()
                            .filter(p -> PathAnalyzer.get(p.getPath()).matches(ctx.path()))
                            .findFirst()
                            .orElse(null);
                    if(ObjectUtil.isNotNull(predicate)) {
                        route = r;
                        break;
                    }
                }
                // 匹配失败
                if(ObjectUtil.isNull(predicate) || ObjectUtil.isNull(route)) {
                    throw ApiCodes.CODE_404;
                }

                // 2、路由断言
                String servicePath = ctx.path();
                // 返回字符串 param2 在字符串 param1 中第 param3 次出现的位置
                int i = StrUtil.ordinalIndexOf(servicePath, "/", predicate.getStripPrefix() + 1);
                servicePath = servicePath.substring(i);
                if(StrUtil.isNotEmpty(predicate.getPrefixPath())) {
                    servicePath = predicate.getPrefixPath().startsWith("/") ? predicate.getPrefixPath() + servicePath : "/" + predicate.getPrefixPath() + servicePath;
                }

                // 3、请求路径拼接请求参数
                if(ctx.paramsMap().size() > 0) {
                    servicePath = StrUtil.builder(servicePath, "?", URLUtil.buildQuery(ctx.paramMap(), Charset.forName(ctx.contentCharset()))).toString();
                }

                // 4、组装 HttpUtils
                HttpUtils httpUtils = HttpUtils.http(route.getUri(), servicePath)
                        // 字符集
                        .charset(ctx.contentCharset())
                        // 请求体
                        .bodyTxt(ctx.body(), ctx.contentType());

                // 5、请求头
                httpUtils.headers(ContextUtil.getHeaders(SecurityUtil.getTokenName()));

                // 6、文件
                if(ctx.isMultipartFormData()) {
                    List<UploadedFile> files = ctx.files("file");
                    if(CollUtil.isEmpty(files)) {
                        throw ApiCodes.CODE_400("文件为空");
                    }
                    for (UploadedFile file : files) {
                        httpUtils.data("file",
                                StrUtil.isEmpty(file.getExtension()) ? Utils.guid() : Utils.guid() + "." + file.getExtension(),
                                file.getContent(),
                                file.getContentType());
                    }
                }

                // 7、转发请求（分布式的特点：转发到别处去），使用服务名转发，即是用“负载均衡”了
                Response res = httpUtils.exec(ctx.method());
                if (!res.isSuccessful()) {
                    throw new ApiCode(res.code(), "请求失败: " + res.message());
                }

                // 8、返回值写入流或转换为 Result（所有微服务都应引入统一返回模块 qingzhou-common-result，里面定义了全局统一返回为 Result）
                if(res.body().contentType().toString().contains(HttpConstant.CONTENT_TYPE_FORM)) {
                    ctx.output(res.body().byteStream());
                    return Result.succeed();
                } else {
                    return JSONUtil.toBean(res.body().string(), Result.class);
                }
            } catch (Exception e) {
                log.error("转发请求出错：{}", e.getMessage());
                throw (e instanceof ApiCode) ? (ApiCode) e : ApiCodes.CODE_400(e);
            }
        }
    }

}
